wikiversitywikiaorg_ru-20200214-history
Программирование и научные вычисления на языке Python/§16
Удобство использование классов для решения проблем математики или физики может быть не таким явным. С другой стороны, во многих программах управления взаимодействия между объектами реального мира программирование с помощью классов выступает как кандидат на первое место. Ниже мы рассмотрим несколько подходящих примеров. Банковские счета Счет в банке отличный пример того как удобно использовать классы. Счет это некоторые данные, обычно имя его владельца, номер счета и текущий баланс. Три вещи, что мы можем сделать со счетом это снять наличные, положить их и распечатать сведения о счете. Эти действия производятся с помощью методов. С помощью класса мы можем совместить данные и действия над ними вместе так, чтобы получить новый тип данных, в котором один счета соответствует одной переменной в программе. Так мы можем создать очень понятный простой класс Account: class Account: def __init__(self, name, account_number, initial_amount): self.name = name self.no = account_number self.balance = initial_amount def deposit(self, amount): self.balance += amount def withdraw(self, amount): self.balance -= amount def dump(self): s = '%s, %s, balance: %s' % \ (self.name, self.no, self.balance) print s И пример того, как класс используется: >>> from classes import Account >>> a1 = Account('John Olsson', '19371554951', 20000) >>> a2 = Account('Liz Olsson', '19371564761', 20000) >>> a1.deposit(1000) >>> a1.withdraw(4000) >>> a2.withdraw(10500) >>> a1.withdraw(3500) >>> print "a1's balance:", a1.balance a1’s balance: 13500 >>> a1.dump() John Olsson, 19371554951, balance: 13500 >>> a2.dump() Liz Olsson, 19371564761, balance: 9500 «Заказчик» класса естественно не хочет чтобы пользователи могли управлять атрибутами экземпляров, то есть изменять имя, номер или баланс. Идея заключается в том, чтобы пользователи могли вызывать только конструктор и методы deposit, withdraw и dump, проверять (при желании) атрибут balnce, но не изменять его. Для таких атрибутов, которые нельзя трогать и методов, что нельзя вызывать, перед названием ставится символ нижнего подчеркивания. Такие имена можно назвать охраняемыми — они как бы принадлежат программе, а не пользователям. Тогда наш «защищенный» (protected) класс AccountP таков: class AccountP: def __init__(self, name, account_number, initial_amount): self._name = name self._no = account_number self._balance = initial_amount def deposit(self, amount): self._balance += amount def withdraw(self, amount): self._balance -= amount def get_balance(self): return self._balance def dump(self): s = '%s, %s, balance: %s' % \ (self._name, self._no, self._balance) print s Телефонная книга Наверняка у вас есть мобильный телефон, а в нем есть телефонная книга, состоящая из списка людей. Современные телефоны позволяют для каждого человека записано его имя, номер телефона, может быть, его электронная почта или какие-то другие данные. Управление телефонной книгой удобно осуществить с помощью класса, который мы назовем, например, Person. Его атрибутами будут имя человека, номер мобильного, номер рабочего телефона, номер домашнего телефона и адрес электронной почты. Добавление данных после инициализации возможно с помощью вызова соответствующих методов: class Person: def __init__(self, name, mobile_phone=None, office_phone=None, private_phone=None, email=None): self.name = name self.mobile = mobile_phone self.office = office_phone self.private = private_phone self.email = email def add_mobile_phone(self, number): self.mobile = number def add_office_phone(self, number): self.office = number def add_private_phone(self, number): self.private = number def add_email(self, address): self.email = address Объекты None позволяет нам добавлять любое количество желаемых сведений кроме имени или не добавлять их вовсе. Пример использования класса: >>> p1 = Person('Hans Hanson', ... office_phone='767828283', email='h@hanshanson.com') >>> p2 = Person('Ole Olsen', office_phone='767828292') >>> p2.add_email('olsen@somemail.net') >>> phone_book = p2 Было бы также неплохо добавить в класс специальный метод для вывода всех данных о человеке: def dump(self): s = self.name + '\n' if self.mobile is not None: s += 'mobile phone: %s\n' % self.mobile if self.office is not None: s += 'office phone: %s\n' % self.office if self.private is not None: s += 'private phone: %s\n' % self.private if self.email is not None: s += 'email address: %s\n' % self.email return s С таким методом несложно распечатать и всю телефонную книгу: >>> for person in phone_book: ... person.dump() ... Hans Hanson office phone: 767828283 email address: h@hanshanson.com Ole Olsen office phone: 767828292 email address: olsen@somemail.net Телефонная книга может быть представлена списком экземпляров класса Person, как показано в примере выше. Однако, если мы хотим быстро по имени просмотреть телефонные номера или e-mail, более удобным оказывается хранение телефонной книги в виде словаря, где имя служит ключом: >>> phone_book = {'Hanson': p1, 'Olsen': p2} >>> for person in sorted(phone_book): # алфавитный порядок ... phone_bookperson.dump() Круг Для задания и быстрого анализа геометрических фигур, например, круга, также отлично подходят классы. Круг на плоскости задается координатами центра (x0, y0) и радиусом R''. Эти три числа мы возьмем как атрибуты класса. В качестве методов будем рассчитывать площадь (area) круга и длину окружности (circumference): class Circle: def __init__(self, x0, y0, R): self.x0, self.y0, self.R = x0, y0, R def area(self): return pi*self.R**2 def circumference(self): return 2*pi*self.R >>> c = Circle(2, -1, 5) >>> print 'A circle with radius %g at (%g, %g) has area %g' % \ ... (c.R, c.x0, c.y0, c.area()) A circle with radius 5 at (2, -1) has area 78.5398 Естественно, идеи класса Circle могут быть распространены и на любые другие геометрические объекты. Специальные методы Методы класса могут иметь имена начинающиеся и заканчивающиеся двойным подчеркиванием. Такой синтаксис показывает что эти методы являются ''специальными. Например, метод конструктора __init__. Этот метод автоматически вызывается при создании экземпляра. Другие специальные методы позволяют производить над экземплярами арифметические операции, сравнивать их, вызывать экземпляры как функции и так далее. __call__ Вычисление значения математической функции, показанное на предыдущем уроке с помощью класса Y и его экземпляров y происходит с помощью вызова y.value(t). Если бы писали y(t), то это выглядело бы как вызов обычной функции. И в действительности такой простой синтаксис возможен с помощью метода __call__. То есть вызов y(t) (в случае если этот метод определен) равносилен y.__call__(t). И этот специальный метод добавить очень просто: class Y: ... def __call__(self, t): return self.v0*t - 0.5*self.g*t**2 Ранее использующийся метод value теперь оказывается лишним. И это хорошая и удобная привычка включать метод __call__ в классы, предоставляющие математические функции. Пример: Производная Имея реализацию математической функции f(x) в виде функции Python, мы хотим получить объект, который принимает эту функцию и находит производную f'(x). Например, если этот объект имеет тип Derivative (производная), мы сможем написать что-то вроде: >>> def f(x): return x**3 ... >>> dfdx = Derivative(f) >>> x = 2 >>> dfdx(x) 12.000000992884452 Как мы знаем производная от x''3 это 3''x''2 и ответ с определенной точностью представления (ошибка в 7 знаке после запятой) и равен 12. Maple, Mathematica и другие математические пакеты используют для целей дифференцирования и интегрирования символьные вычисления. Также имеется библиотека Python — SumPy, бесплатная и легко используемая для многих видов функций ''f(x). Однако для целого ряда задач, например, таких как случайные числа, использование символьных вычислений представляется проблемой и требуется численный расчет. Для нашей задачи численного дифференцирования мы будем использовать не самую точную, но зато самую простую формулу. Конечно, вы сами можете использовать более точные методы, что захотите. Идея сейчас в том, чтобы создать класс, с помощью которого мы будем дифференцировать функцию f с заданным шагом h. Чем меньше h, тем точнее и дольше вычисления. Эти переменные мы устанавливаем в конструкторе. Оператор __call__ рассчитывает производную в точке x по известной формуле: class Derivative: def __init__(self, f, h=1E-9): self.f = f self.h = float(h) def __call__(self, x): f, h = self.f, self.h return (f(x+h) - f(x))/h >>> from math import sin, cos, pi >>> df = Derivative(sin) >>> x = pi >>> df(x) -1.000000082740371 >>> cos(x) # точно -1.0 >>> def g(t): ... return t**3 ... >>> dg = Derivative(g) >>> t = 1 >>> dg(t) # если точно, то должно быть 3 3.000000248221113 __str__ Другой специальный метод это __str__. Он вызывается, когда экземпляр класса должен быть представлен в виде строки: print a, где a — экземпляр. Если Python для a находит специальный метод __str__, то он печатает его в соответствии с методом. Аналогично добавлению в класс Y метода __call__, мы можем усовершенствовать его и с помощью метода __str__ class Y: ... def __str__(self): return 'v0*t - 0.5*self.g*t**2; v0=%g' % self.v0 Таким образом метод __str__ выгодно замещает наш старый метод formula точно так же как __call__ заменил value. Таким образом, опытные программисты на Python могли решить нашу задачу из предыдущего урока с использованием только специальных методов, оказывающихся и более естественными и удобными: class Y: def __init__(self, v0): self.v0 = v0 self.g = 9.81 def __call__(self, t): return self.v0*t - 0.5*self.g*t**2 def __str__(self): return 'v0*t - 0.5*g*t**2; v0=%g' % self.v0 И тут же видим более ясное применение в действии: >>> y = Y(1.5) >>> y(0.2) 0.1038 >>> print y v0*t - 0.5*g*t**2; v0=1.5 __add__ и другие Пусть у нас есть два экземпляра класса C: a и b. И мы хотели бы задать определенный смысл выражению a + b, чтобы Python встречая знак сложения для экземпляров этого класса производил определенную операцию. Для этого существует метод __add__: class C: ... __add__(self, other): ... Метод __add__ складывает экземпляры self и other и возвращает результат как экземпляр. То есть когда Python встречает для наших экземпляров выражение a + b он вызывает метод a.__add__(b). Кроме операции сложения возможно еще множество подобных операций и других специальных методов: Категория:Программирование и научные вычисления на языке Python